﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using VsxFactory.Modeling.VisualStudio;
using System.IO.Packaging;
using System.Net;
using System.Reflection;
using System.Xml.Linq;
using VsxFactory.Modeling.Strategies;

namespace VsxFactory.Modeling.Repository
{
    public abstract class ReferencePackageRepository : IStandardPackageRepository
    {
        protected static readonly string PackageReferenceFile = "strategies.config";

        protected string Kind { get; private set; }
        protected IStrategyContext Context { get; private set; }
        private List<PackageInfo> _standardStrategies = new List<PackageInfo>();

        protected ReferencePackageRepository(IStrategyContext context, string packageKind)
        {
            System.Diagnostics.Contracts.Contract.Requires(context != null);
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(packageKind));
            Kind = packageKind;
            Context = context;
        }

        protected abstract XDocument GetDocument(bool createIfNotExists=false);
        public abstract PackageSource Source { get; }

        protected virtual List<PackageInfo> PackageCache { get; set; }

        public virtual IQueryable<PackageInfo> GetPackages()
        {
            if (PackageCache == null)
            {
                XDocument document = GetDocument();
                if (document == null)
                {
                    PackageCache = new List<PackageInfo>();
                }
                else
                {
                    var packages = from packageElement in document.Root.Elements("package")
                               let version = packageElement.Attribute("version").Value
                               let requireLicenseAcceptance = (string)packageElement.Attribute("requireLicenseAcceptance")
                               select PreparePackageInfo(new PackageInfo()
                               {
                                   Version = version,
                                   Kind = (string)packageElement.Attribute("kind") ?? PackageKind.Strategy,
                                   LicenseUrl = (string)packageElement.Attribute("licenseUrl") ?? String.Empty,
                                   RequireLicenseAcceptance = requireLicenseAcceptance != null && requireLicenseAcceptance.Equals("true", StringComparison.InvariantCultureIgnoreCase) ? true : false,
                                   Authors = (string)packageElement.Attribute("authors") ?? string.Empty,
                                   Name = (string)packageElement.Attribute("name"),
                                   Description = (string)packageElement.Attribute("description") ?? String.Empty,
                                   RepositorySource = (string)packageElement.Attribute("src") ?? String.Empty
                               });
                    PackageCache = packages.ToList();
                }
            }
            return PackageCache.Concat(_standardStrategies).AsQueryable();
        }

        public void RegisterStandardProvider(Lazy<IStrategyProvider, IStrategyProviderMetadata> value)
        {
            var package = new PackageInfo();
            package.Name = value.Metadata.PackageName;
            package.Description = value.Metadata.Description;
            package.Kind = PackageKind.System;
            package.Version = value.Value.GetType().Assembly.GetName().Version.ToString();
            package.Authors = VsxFactoryConstants.Name;
            if(!_standardStrategies.Any(s=>s.ID == package.ID))
                _standardStrategies.Add(package);
        }

        protected virtual PackageInfo PreparePackageInfo(PackageInfo package)
        {
            return package;
        }

        public virtual void InstallPackage(PackageInfo package)
        {
            XDocument document = GetDocument(createIfNotExists: true);
           
            var node = (from p in document.Root.Elements("package")
                        where String.Compare(p.Attribute("id").Value, package.ID, StringComparison.InvariantCultureIgnoreCase) == 0
                        select p).FirstOrDefault();
            if (node != null)
                node.Remove();

            AddPackageInfo(document, package);

            if(PackageCache != null)
            {
                PackageCache.RemoveAll(p => String.Compare(p.ID, package.ID, StringComparison.InvariantCultureIgnoreCase) == 0);
                PackageCache.Add(package);
            }

            SaveDocument(document);
        }

        protected void AddPackageInfo(XDocument document, PackageInfo package)
        {
            document.Root.Add(new XElement("package",
                                            new XAttribute("id", package.ID),
                                            new XAttribute("kind", package.Kind),
                                            new XAttribute("version", package.Version),
                                            new XAttribute("description", package.Description ?? String.Empty),
                                            new XAttribute("src", package.RepositorySource ?? String.Empty),
                                            new XAttribute("authors", package.Authors ?? String.Empty),
                                            new XAttribute("licenseUrl", package.LicenseUrl ?? String.Empty),
                                            new XAttribute("requireLicense", package.RequireLicenseAcceptance),
                                            new XAttribute("name", package.Name)
                                            ));
        }

        public virtual void Uninstall(PackageInfo package)
        {
            XDocument document = GetDocument();

            // If there is no document then do nothing
            if (document == null)
            {
                return;
            }

            XElement packageElement = (from e in document.Descendants("package")
                                       let id = e.Attribute("id").Value
                                       where package.ID.Equals(id, StringComparison.OrdinalIgnoreCase) 
                                       select e).FirstOrDefault();

            // Remove the element from the xml dom
            if (packageElement != null)
            {
                if( PackageCache != null)
                    PackageCache.RemoveAll(p => package.ID.Equals(p.ID, StringComparison.OrdinalIgnoreCase));
                packageElement.Remove();
            }

            SaveDocument(document);
        }

        protected abstract void SaveDocument(XDocument document);

        /// <summary>
        /// Handles the AssemblyResolve event of the CurrentDomain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
        /// <returns></returns>
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            return Assembly.GetExecutingAssembly();
        }

        public virtual void SynchronizeStrategies()
        {            
        }
    }
}
